#include "table.h"

#ifdef TEST_TABLE
#define PRIVATE 
#else
#define PRIVATE static
#endif

PRIVATE void checkConsistent(table_t *tbl);

table_t *createTable(int type)
{
    table_t *table = (table_t *) malloc(sizeof(table_t));
    if (table == NULL) {
        fprintf(stderr, "create table error\n");
        exit(EXIT_FAILURE);
    }

    table->type = type;
    table->pager = createPager(table->type);
    table->record_count = 0;
    table->tree = InitTree(table->pager);

    return table;
}

void destroyTable(table_t *table)
{
    DestroyTree(table->tree);
    destroyPager(table->pager);
    free(table);
    // printf("\n");
}

int insert(table_t *table, record_t *record)
{
    if (table->record_count == MAX_RECORDS_NUM) {
        fprintf(stderr, "table is full\n");
        return 1;
    }

    int is_duplicate_key = 0;
    table->tree = InsertToTree(table->tree, record->index, table->pager, record, &is_duplicate_key);
    if (is_duplicate_key != 0) {
        printf("DUPLICATE KEY: %d\n", record->index);
    } else {
        table->record_count += 1;
    }

    checkConsistent(table);

    return 0;
}


int delete(table_t *table, int index)
{
    if (table->record_count == 0) {
        fprintf(stderr, "table is empty\n");
        return 1;
    }

    table->tree = RemoveFromTree(table->tree, index);
    table->record_count -= 1;

    checkConsistent(table);

    return 0;
}

int search(table_t *table, int index, record_t *record)
{
    Position node = SearchFromTree(table->tree, index);
    if (node == NULL) {
        fprintf(stderr, "there is no such key: %d\n", index);
        return 1;
    }

    char *start = getRecordFromPage(&(node->page), index);
    record->table = table->type;
    record->index = index;
    if (deserialize(start, record) != 0) 
        return 1;

    return 0;
}

int modify(table_t *table, int index, record_t *record)
{
    if (delete(table, index) != 0)
        return 1;
    if (insert(table, record) != 0)
        return 1;

    return 0;
}

void initrettbl(rettbl_t *rettbl)
{
    rettbl->colnum = 0;
    rettbl->valnum = 0;
    rettbl->bufsz = 20;
    rettbl->values = (char **) malloc(sizeof(char *) * (rettbl->bufsz));
    for (int i = 0; i < 20; i++) {
        rettbl->colrefs[i] = NULL;
    }
}

int addcolref(rettbl_t *rettbl, const char *col)
{
    int max_token_len = 20;
    rettbl->colrefs[rettbl->colnum] = (char *) malloc(max_token_len);
    if (rettbl->colrefs[rettbl->colnum] == NULL)
        return -1;

    strncpy(rettbl->colrefs[rettbl->colnum], col, max_token_len);
    rettbl->colrefs[rettbl->colnum][max_token_len - 1] = '\0';

    rettbl->colnum++;

    return 0;
}

int addvalue(rettbl_t *rettbl, record_t *record)
{
    if (rettbl->valnum == rettbl->bufsz) {
        rettbl->bufsz *= 2;
        rettbl->values = (char **) realloc(rettbl->values, sizeof(char *) * (rettbl->bufsz));
        if (rettbl->values == NULL)
            return -1;
    }
    rettbl->values[rettbl->valnum] = (char *) malloc(sizeof(record_t));
    if (rettbl->values[rettbl->valnum] == NULL)
        return -1;

    char *buf = rettbl->values[rettbl->valnum];

    switch (record->table) {
        case USER:
            snprintf(buf, sizeof(record_t), "%hd;%s;%s", 
                record->val_user.user_id, record->val_user.name, record->val_user.email);
            break;

        case BORROW:
            snprintf(buf, sizeof(record_t), "%hd;%hd;%hd;%hd-%hhd-%hhd;%hd-%hhd-%hhd;%hhd",
                record->val_borrow.borrow_id, record->val_borrow.book_id, record->val_borrow.user_id,
                record->val_borrow.borrow_date.year, record->val_borrow.borrow_date.month, record->val_borrow.borrow_date.day,
                record->val_borrow.return_date.year, record->val_borrow.return_date.month, record->val_borrow.return_date.day,
                record->val_borrow.status);
            break;

        case BOOK:
            snprintf(buf, sizeof(record_t), "%hd;%hd;%hd;%hhd;%s;%s",
                record->val_book.book_id, record->val_book.quantity, record->val_book.available,
                record->val_book.category, record->val_book.title, record->val_book.author);
            break;

        default:
            return -1;
    }

    rettbl->valnum++;

    return 0;
}

int addtblref(rettbl_t *rettbl, int type)
{
    switch (type) {
        case USER:
            addcolref(rettbl, "user_id");
            addcolref(rettbl, "name");
            addcolref(rettbl, "email");
            break;

        case BORROW:
            addcolref(rettbl, "borrow_id");
            addcolref(rettbl, "book_id");
            addcolref(rettbl, "user_id");
            addcolref(rettbl, "borrow_date");
            addcolref(rettbl, "return_date");
            addcolref(rettbl, "status");
            break;

        case BOOK:
            addcolref(rettbl, "book_id");
            addcolref(rettbl, "quantity");
            addcolref(rettbl, "available");
            addcolref(rettbl, "category");
            addcolref(rettbl, "title");
            addcolref(rettbl, "author");
            break;

        default:
            return -1;
    }

    return 0;
}

int getRecord(table_t *table, int pos, record_t *record)
{
    Position cur = table->tree;
    if (cur == NULL) return -1;

    while (cur->Children[0] != NULL)
        cur = cur->Children[0];

    int count = 0;
    do {
        count += cur->KeyNum;
        if (count > pos) {
            break;
        }
        cur = cur->Next;
    } while (cur != NULL);

    if (cur == NULL) return -1;

    record->table = table->type;
    int off = pos - (count - cur->KeyNum);
    page_t *page = &(cur->page);
    char *start = (char *) page->end - (int) (page->slot + off)->off;
    deserialize(start, record);

    switch (record->table) {
        case USER: record->index = record->val_user.user_id; break;
        case BORROW: record->index = record->val_borrow.borrow_id; break;
        case BOOK: record->index = record->val_book.book_id; break;
        default: return -1;
    }

    return 0;
}

void destroyrettbl(rettbl_t *rettbl)
{
    for (int i = 0; i < rettbl->valnum; i++) {
        free(rettbl->values[i]);
    }
    free(rettbl->values);
    rettbl->values = NULL;

    rettbl->bufsz = 0;
    rettbl->colnum = 0;
    rettbl->valnum = 0;
}

int flushPages(table_t *table, FILE *file)
{
    if (table->record_count == 0) {
        return 0;
    }

    Position cur = table->tree;
    while (cur->Children[0] != NULL)
        cur = cur->Children[0];

    while (cur != NULL) {
        if (flushPageToDisk(&(cur->page), file) != 0)
            return -1;
        cur = cur->Next;
    }

    return 0;
}

int loadPages(table_t *table, FILE *file)
{
    page_t page;
    record_t record;

    while (1) {
        /* load page */
        if (getPage(table->pager, &page) != 0)
            break;
        if (loadPageFromDisk(&page, file) != 0)
            break;

        if (PAGE_GET(page.type) != table->type) {
            fseek(file, -PAGE_SIZE, SEEK_CUR);
            cleanPage(&page);
            break;
        }

        /* rebuild index */
        for (int i = 0; i < PAGE_RECORD_NUM(&page); i++) {
            int idx = (page.slot + i)->index;
            char *start = getRecordFromPage(&page, idx);
            record.table = table->type;
            record.index = idx;
            deserialize(start, &record);

            insert(table, &record);
        }

        cleanPage(&page);
    }

    return 0;
}

/*
 * check if leaf node's keynum is equal to page's record num
 */
PRIVATE void checkConsistent(table_t *tbl)
{
    Position cur = tbl->tree;
    while (cur->Children[0] != NULL)
        cur = cur->Children[0];
    while (cur != NULL) {
        assert(cur->KeyNum == PAGE_RECORD_NUM(&(cur->page)));
        cur = cur->Next;
    }
}